#!/bin/env python

import math

## twisted imports
from twisted.internet import gtk2reactor # for gtk-2.0
gtk2reactor.install()
from twisted.internet import defer, reactor, threads
from twisted.web import client

## kiwi imports
from kiwi.ui.delegates import SlaveDelegate, ProxyDelegate
from kiwi.ui.proxy import Proxy
from kiwi.ui.objectlist import Column, ObjectList
from kiwi.model import Model


## gtk imports
import pygtk
pygtk.require('2.0')

import gtk
from gtk import gdk

try:
    import cairo
except ImportError:
    pass

if gtk.pygtk_version < (2,7,99):
    print "PyGtk 2.7.99 or later required"
    raise SystemExit

def parse_float(value):
    if isinstance(value, unicode):
            rtn = float(value.replace(',',''))
    else:
            rtn = float(value)
    return rtn

class Stock(Model):
    def __init__(self,stk_id,symbol,name,price=0,shares=0,buzz=0):
        Model.__init__(self)
        try:
            self.stk_id = int(stk_id)
        except:
            print stk_id,symbol,name
            
        self.symbol = symbol
        self.name   = name
        self.set_price(price)
        self.set_shares(shares)
        self.set_buzz(buzz)
        self.history=()
        pass

    
    def set_price(self, price):
        self.price  = parse_float(price)
        pass
    
    def set_shares(self, shares):        
        self.shares = int(shares)
        pass

    def set_buzz(self, buzz):
        self.buzz   = parse_float(buzz)
        pass

    def set_market(self, market):
        self.market = market
        pass

    def __repr__(self):
        return "%s (%s): %.2f" % (self.name, self.symbol, self.price)

    def calc_dividend(self):
        """What is a dividend payment?

        All markets close every Friday from 6pm to 9pm US Eastern
        Time. During that time, dividends are paid according to actual
        buzz scores. Each market receives a dividend of 3% of its
        capitalization every week. Dividends are divided among stocks
        in a market according to buzz scores. For example, if on
        Friday at 6pm ET the buzz score for Internet Explorer (IE) is
        80 and the buzz score for Mozilla (MOZFF) is 20, and the total
        Browser Wars market cap is $500,000, then the total dividend
        for IE will be $12,000 (or $500,000 x 3% x 80%) and the total
        dividend for MOZFF will be $3,000 (or $500,000 x 3% x
        20%). The dividend for each stock is then distributed to
        shareholders, with each share receiving an equal portion.

        http://buzz.research.yahoo.com/dm/info/help.html        
        """
        total_dividends = self.market.mktcap * 0.03 * self.buzz / 100
        return total_dividends / self.shares

    def get_dividend_return(self, amount=1000):
        """how much dividends will return this stock on friday if I
        invest 'amount' ..?"""
        
        return math.floor(amount / self.price) * self.calc_dividend()

    def get_market(self):
        return "%s (%s)" % (self.market.name, self.market.symbol)

    def get_formated_name(self):
        return "%s (%s)" % (self.symbol, self.market.symbol)

    def get_history(self):
        return self.history.__repr__()

    def update_history(self, from_date, to_date):
        url1='http://localhost:8080/extended/history.xml?stockid=%d&from_date=%s&to_date=%s' % (self.stk_id, from_date, to_date)
        deferred1 = client.getPage(url1)
        deferred1.addCallback(self.do_update_history)
        import sys
        def printError(failure):
            print >> sys.stderr, "Error:", failure
        deferred1.addErrback(printError)
        
        return deferred1

    def do_update_history(self, xml_contents):
        self.history = parse_history(xml_contents, self)
        import pprint
        pprint.pprint(self.history)
        

    pass

class Market(Model):
    def __init__(self, mkt_id, symbol, name, shares = 0, mktcap = 0):
        Model.__init__(self)
        self.stocks   = []
        self.mkt_id = mkt_id
        self.symbol = symbol
        self.name   = name
        self.set_shares(shares)
        self.set_mktcap(mktcap)
        pass

    def addStock(self, stock):
        stock.set_market(self)
        self.stocks.append(stock)
        pass

    def __iter__(self):
        return self.stocks.__iter__()

    def set_shares(self, value):
        self.shares = int(value)
        pass

    def set_mktcap(self, value):
        self.mktcap = parse_float(value)
        pass


    def getStock(self, stk_id):
        for s in self.stocks:
            if s.stk_id == int(stk_id):
                return s
        return None
            
    def __repr__(self):
        return "%s (%s)" % (self.name, self.symbol)

    pass

class Account(Model):
    def __init__(self, username):
        Model.__init__(self)
        self.username = username        
        self.holdings = []
        self.trades = []
        pass

    def set_rank(self, rank):
        self.rank = int(rank)
        pass

    def set_networth(self, networth):
        self.networth = parse_float(networth)
        pass

    def set_deposits(self, deposits):
        self.deposits = parse_float(deposits)
        pass

    def set_cash(self, cash):
        self.cash = parse_float(cash)
        pass

    def set_gain(self, gain):
        self.gain = parse_float(gain)
        pass

    def set_acc_value(self, value):
        self.acc_value = parse_float(value)
        pass       

    def set_holdings(self, holdings):
        self.holdings = holdings
        pass       

    def set_trades(self, trades):
        self.trades = trades
        pass       

    def __repr__(self):
        return "Account '%s' ranked %d\n networth: %.2f\n deposits: %.2f\n cash: %.2f\n gain: %.2f\n value: %.2f" % (self.username, self.rank, self.networth, self.deposits, self.cash, self.gain, self.acc_value)
    pass

class Holding(Model):
    def __init__(self, stk, quantity, cost):
        Model.__init__(self)
        self.stk = stk
        print stk
        self.quantity = int(quantity)
        self.cost = parse_float(cost)
        pass

    def get_total_cost(self):
        return self.quantity * self.cost

    def get_actual_price(self):
        return self.stk.price

    def get_total_price(self):
        return self.quantity * self.get_actual_price()

    def get_estimated_dividends(self):
        return self.stk.calc_dividend() * self.quantity

    def get_unrealized_profit_loss(self):
        ''' TODO: CHECK the differencew between webpage and this calculation'''
        return self.get_total_price() - self.get_total_cost() 

    def get_roi(self):
        return self.get_unrealized_profit_loss() * 100.0 / self.get_total_cost()
        
    def get_formatted_name(self):
        return "%s (%s)" % (self.stk.symbol, self.stk.market.symbol)
    
    def __repr__(self):
        return "%s: %d * %.2f = %.2f" % (self.stk.__repr__(), self.quantity, self.cost, self.get_total_cost())    


    pass

class Trade(Model):
    def __init__(self, stk_id, date, typestr, quantity, cost):
        Model.__init__(self)
        self.stk_id = stk_id
        self.date = date
        self.type = typestr
        self.quantity = quantity
        self.cost = cost
        pass
    pass


class BuzzGame(Model):
    def __init__(self):
        Model.__init__(self)
        self.markets=[]
        self.stocks=[]
        self.accounts=[]
        self.state = 'booting'
        pass

    def addMarket(self, market):
        self.markets.append(market)
        pass
    
    def addStock(self, stock):
        self.stocks.append(stock)
        pass
    
    def addAccount(self, account):
        self.accounts.append(account)
        pass
    
    def __iter__(self):
        return self.markets.__iter__()

    def getStock(self, stk_id):
        for s in self.stocks:
            if s.stk_id == int(stk_id):
                return s 
        print str("stk_id %s no existe" % stk_id)

    def set_state(self, value):
        self.state = value
    pass

import xml.dom.minidom
from xml.xpath.Context import Context
from xml.xpath import Evaluate
from xml.xpath.NamespaceNode import NamespaceNode

def cnvtClean(e):
    return [x for x in e if str(x.__class__) != 'xml.dom.minidom.Text' ]

def parse_stock(dom_child):
    
    s = Stock(dom_child.getAttribute('id'),
              dom_child.getAttribute('symbol'),
              dom_child.getAttribute('name'))


    childs = cnvtClean( dom_child.childNodes )
    for child in childs:
        if child.nodeName == "Price":
               s.set_price(child.childNodes[0].data)
        elif child.nodeName == "Buzz":
               s.set_buzz(child.childNodes[0].data)
        elif child.nodeName == "Shares":
               s.set_shares(child.childNodes[0].data)
        else:
            print "unknown xml element", child.nodeName, "in", s
    return s

def parse_allinfo(content):
    print "got allinfo.xml | Lenght: ", len(content)
    buzzgame = BuzzGame()

    inputDom = xml.dom.minidom.parseString(content)
    c = Context(inputDom.documentElement)

    xpathstr='/ResultSet/Market'
    result = Evaluate(xpathstr, context=c)

    for d in result:
        m = Market(d.getAttribute('id'),
                   d.getAttribute('symbol'),
                   d.getAttribute('name'))

        childs = cnvtClean( d.childNodes )
        for child in childs:
            if child.nodeName == "Stock":
                stock = parse_stock(child)
                m.addStock(stock)
                buzzgame.addStock(stock)
            elif child.nodeName == "Shares":
                m.set_shares(child.childNodes[0].data)
            elif child.nodeName == "MktCap":
                m.set_mktcap(child.childNodes[0].data)
            else:
                print "unknown xml element", child.nodeName, "in", m

        buzzgame.addMarket(m)
    buzzgame.set_state("listo...")
    return buzzgame

def parse_account(contents, username):
    print "got account.xml for '",username,"' | Lenght: ", len(contents)
    inputDom = xml.dom.minidom.parseString(contents)
    c = Context(inputDom.documentElement)

    xpathstr='/ResultSet/Account'
    result = Evaluate(xpathstr, context=c)

    assert(len(result), 1)

    rtn = Account(username)

    account = result[0]
    childs = cnvtClean(account.childNodes)
    for child in childs:
        if child.nodeName == "Rank":
            rtn.set_rank(child.childNodes[0].data)
        elif child.nodeName == "NetWorth":
            rtn.set_networth(child.childNodes[0].data)
        elif child.nodeName == "Deposits":
            rtn.set_deposits(child.childNodes[0].data)
        elif child.nodeName == "Cash":
            rtn.set_cash(child.childNodes[0].data)
        elif child.nodeName == "Gain":
            rtn.set_gain(child.childNodes[0].data)
        elif child.nodeName == "Value":
            rtn.set_acc_value(child.childNodes[0].data)
        else:
            print "unknown xml element", child.nodeName, "in", account
    return rtn

def parse_holdings(contents, game):
    print "got holdings.xml | Lenght: ", len(contents)
    inputDom = xml.dom.minidom.parseString(contents)
    c = Context(inputDom.documentElement)

    xpathstr='/ResultSet/Holding'
    result = Evaluate(xpathstr, context=c)

    rtn = []

    for h in result:
        childs = cnvtClean(h.childNodes)
        for child in childs:
            if child.nodeName == "Id":
                stk_id = child.childNodes[0].data
            elif child.nodeName == "Quantity":
                quantity = child.childNodes[0].data
            elif child.nodeName == "Cost":
                cost = child.childNodes[0].data
        rtn.append(Holding(game.getStock(stk_id), quantity, cost))

    return rtn

def parse_trades(contents):
    print "got trades.xml | Lenght: ", len(contents)
    inputDom = xml.dom.minidom.parseString(contents)
    c = Context(inputDom.documentElement)

    xpathstr='/ResultSet/Trade'
    result = Evaluate(xpathstr, context=c)

    rtn = []

    for h in result:
        childs = cnvtClean(h.childNodes)
        for child in childs:
            if child.nodeName == "StockId":
                stk_id = child.childNodes[0].data
            elif child.nodeName == "Date":
                date = child.childNodes[0].data
            elif child.nodeName == "Type":
                typestr = child.childNodes[0].data
            elif child.nodeName == "Quantity":
                quantity = child.childNodes[0].data
            elif child.nodeName == "Cost":
                cost = child.childNodes[0].data
                
        rtn.append(Trade(stk_id, date, typestr, quantity, cost))

    return rtn


def parse_history(contents, stock):
    print "got history.xml | Lenght: ", len(contents)
    inputDom = xml.dom.minidom.parseString(contents)
    c = Context(inputDom.documentElement)

    xpathstr='/ResultSet/Stock/Point'
    result = Evaluate(xpathstr, context=c)

    rtn = []

    for h in result:
        childs = cnvtClean(h.childNodes)
        for child in childs:
            if child.nodeName == "Date":
                date = child.childNodes[0].data
            elif child.nodeName == "Price":
                price = child.childNodes[0].data
        rtn.append((date, float(price)))

    return rtn

class HistoryWidget(gtk.DrawingArea):
    def __init__(self):
        gtk.DrawingArea.__init__(self)
        self.red=100
        self.add_events(gtk.gdk.BUTTON_PRESS_MASK |
                        gtk.gdk.BUTTON_RELEASE_MASK |
                        gtk.gdk.BUTTON1_MOTION_MASK |
                        gtk.gdk.SCROLL_MASK
                        )

        self.connect("button_press_event", self.on_button_press_event)
        self.connect("expose_event", self.do_expose_event)

    def do_expose_event(self,w, event):
        try:
            self.context = self.window.cairo_create()
        except AttributeError:
            #return self._expose_gdk(event)
            raise
        return self._expose_cairo(event)

    def _expose_cairo(self, event):
        self.context.rectangle(event.area.x, event.area.y,
                     event.area.width, event.area.height)
        self.context.clip()	
        self.draw()

    def refresh(self):
        print "do_refresh"
        x, y, w, h = self.allocation
        self.window.invalidate_rect((0,0,w,h),False)

    def draw(self):
        print "draw"
        # background
        x, y, w, h = self.allocation
        try:
            self.context.rectangle (0,0,w,h)
            self.context.set_source_rgb(self.red, 0, 0) 
            self.context.fill ()

            self.context.rectangle (0,0,w,h)
            self.context.set_line_width(10)
            self.context.set_source_rgb(0, 0, 0) 
            self.context.stroke()
            

            
        except cairo.Error, e:
            # FIXME: says "cairo.Error: the target surface has been finished"
            print "!", e
            pass

    def on_button_press_event(self, w,e):
        import random
        self.red = random.randint(0, 100) / 100.0
        self.refresh()

        

class ProxyAsker(ProxyDelegate):
    def __init__(self, game):
        ProxyDelegate.__init__(self, game, ['state'],
                               gladefile="main",
                               delete_handler=self.salir)
        #--------------------
        self.plist = ObjectList([
                                 Column('dividend_return', data_type=float, format="%.2f", title="DR", sorted=True),
                                 Column('formated_name', data_type=str),
                                 Column('market', data_type=str, visible=False),
                                 Column('price', data_type=float, format="%.2f"),
                                 Column('shares', data_type=int),
                                 Column('buzz', data_type=float, format="%.2f"),
                                 ])

        self.plist.connect('row-activated', self.slave_row_activated)
        self.plist.add_list(game.stocks)
        #--------------------
        slave = SlaveDelegate(toplevel=self.plist)
        self.attach_slave("stocks", slave)
        #--------------------
        self.alist = ObjectList([Column('username', data_type=str),
                                 Column('rank', data_type=int),
                                 ])

        self.alist.add_list(game.accounts)
        #--------------------
        view2 = SlaveDelegate(gladefile="account")
        widgets=['username',
                 'rank',
                 'networth',
                 'deposits',
                 'gain',
                 'cash',
                 'acc_value'] 
        self.account_proxy = Proxy(view2, game.accounts[0], widgets)
        self.attach_slave("accounts", view2)


        self.hlist = ObjectList([Column('formatted_name', data_type=str),
                                 Column('quantity', data_type=int),
                                 Column('cost', data_type=float, format="%0.2f"),
                                 Column('total_cost', data_type=float, format="%0.2f"),
                                 Column('actual_price', data_type=float, format="%0.2f"),
                                 Column('total_price', data_type=float, format="%0.2f"),
                                 Column('unrealized_profit_loss', data_type=float, format="%0.2f"),
                                 Column('estimated_dividends', data_type=float, format="%0.2f"),
                                 Column('roi', data_type=float, format="%0.2f"),
                                 ])

        self.hlist.add_list(game.accounts[0].holdings)
        #--------------------
        slave3 = SlaveDelegate(toplevel=self.hlist)
        self.attach_slave("holdings", slave3)

        t = HistoryWidget()
        slave = SlaveDelegate(toplevel=t)
        self.history_proxy = Proxy(slave)
        self.attach_slave("history", slave)

        return


    def slave_row_activated(self, widget, stock):
        stock.update_history('0', '9999')
        

    def salir(self,widget,event):
        reactor.stop()
    pass

def show_ui(game):
    proxy = ProxyAsker(game)
    proxy.show_all()
    print game.accounts
    return

def process_all(results, username):
    print "processing all..."
    buzzgame = parse_allinfo(results[0][1])
    acc = parse_account(results[1][1], username)
    acc.set_holdings(parse_holdings(results[2][1], buzzgame))
    acc.set_trades(parse_trades(results[3][1]))
    buzzgame.addAccount(acc)
    return buzzgame

def do_getFile(filename):
    print "reading...", filename
    ret = "\n".join(file(filename,'r').readlines())
    return ret

def main():
    import sys
    def printError(failure):
        print >> sys.stderr, "Error:", failure

    username = 'ncesar'
    password = 'seguridad'

    url1='http://buzz.research.yahoo.com/bk/rest/allinfo.xml'
    #url1='http://localhost:8080/bk/rest/allinfo.xml'
    deferred1 = client.getPage(url1)
    #deferred1=threads.deferToThread(do_getFile,'allinfo.xml')

    url2='https://buzz.research.yahoo.com/bk/rest/account.xml?login=%s&password=%s' % (username, password)
    deferred2 = client.getPage(url2)
    #deferred2=threads.deferToThread(do_getFile,'account.xml')
    
    url3='https://buzz.research.yahoo.com/bk/rest/holdings.xml?login=%s&password=%s' % (username, password)
    deferred3 = client.getPage(url3)
    #deferred3=threads.deferToThread(do_getFile,'holdings.xml')

    url4='https://buzz.research.yahoo.com/bk/rest/trades.xml?login=%s&password=%s' % (username, password)
    deferred4 = client.getPage(url4)
    #deferred4=threads.deferToThread(do_getFile,'trades.xml')

    # Creates a DeferredList
    dl = defer.DeferredList([deferred1, deferred2, deferred3, deferred4])
    dl.addCallback(process_all, username)
    dl.addErrback(printError)
    dl.addCallback(show_ui)
    dl.addErrback(printError)
    reactor.run()
    

if __name__ == '__main__':
    main()
